// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <tests/paser/node_check.h>

namespace cantor::parser {
    void check_node_info(Node *node, const nlohmann::ordered_json &result) {
        ASSERT_EQ(node->is_complex, result["is_complex"].get<bool>());
        auto &loc = result["location"];
        ASSERT_EQ(node->location.location, loc["location"].get<int>());
        ASSERT_EQ(node->location.len, loc["len"].get<int>());
    }

    void check_parse(ParseError e, const nlohmann::ordered_json &result) {
        std::string err(parse_error_to_string(e));
        ASSERT_EQ(err, result["errcode"].get<std::string>());
    }

    void check_parse(ParseError e, const std::string &errmsg, const nlohmann::ordered_json &result) {
        std::string err(parse_error_to_string(e));
        ASSERT_EQ(err, result["errcode"].get<std::string>());
        ASSERT_EQ(errmsg, result["errmsg"].get<std::string>());
    }

    void check_constant_expr_node_info(ConstantExpr *expr, const nlohmann::ordered_json &result) {
        check_expr_node_info(expr, result);
        ASSERT_EQ(pb::ast::ConstantExprType_Name(expr->literal_type), result["literal_type"].get<std::string>());
        switch (expr->literal_type) {
            case pb::ast::CET_INT:
                ASSERT_EQ(expr->_u.int64_val, result["value"].get<int64_t>());
                break;
            case pb::ast::CET_DOUBLE:
                ASSERT_EQ(expr->_u.double_val, result["value"].get<double>());
                break;
            case pb::ast::CET_STRING:
                ASSERT_EQ(std::string(expr->_u.str_val.value), result["value"].get<std::string>());
                break;
            case pb::ast::CET_BOOL:
                ASSERT_EQ(expr->_u.bool_val, result["value"].get<bool>());
                break;
            case pb::ast::CET_NULL:
                ASSERT_EQ("NULL", result["value"].get<std::string>());
                break;
            case pb::ast::CET_PLACE_HOLDER:
                ASSERT_EQ("?", result["value"].get<std::string>());
                break;
            case pb::ast::CET_HEX:
                ASSERT_EQ(turbo::str_cat("0x", expr->row_str.value), result["value"].get<std::string>());
                break;
            case pb::ast::CET_MAXVALUE:
                ASSERT_EQ("LT_MAXVALUE", result["value"].get<std::string>());
                break;
            case pb::ast::CET_MINVALUE:
                ASSERT_EQ("LT_MINVALUE", result["value"].get<std::string>());
                break;
            case pb::ast::CET_NONE:
            default:
                ASSERT_EQ("LT_NONE", result["value"].get<std::string>());
        }
    }

    void check_range_var(RangeVar *expr, const nlohmann::ordered_json &result) {
        /*
        check_expr_node_info(expr, result);
        auto names = result["names"].get<std::vector<std::string> >();
        ASSERT_EQ(names.size(), expr->names.size());
        for (size_t i = 0; i < names.size(); i++) {
            ASSERT_EQ(names[i], std::string(expr->names[i].value));
        }*/
    }

    void check_var_assign(const VarAssign *assign, const nlohmann::ordered_json &json_var) {
        ASSERT_EQ(std::string(assign->key.value), json_var["key"].get<std::string>());
        auto &value = json_var["value"];
        if (assign->value->expr_type == pb::ast::ENT_CONSTANT_EXPR) {
            ConstantExpr *literal = (ConstantExpr *) (assign->value);
            check_constant_expr_node_info(literal, value);
            return;
        }
        if (assign->value->expr_type == pb::ast::ENT_RANGE_VAR) {
            RangeVar *literal = (RangeVar *) (assign->value);
            check_range_var(literal, value);
            return;
        }
    }

    void check_type_numeric(TypeStmt *stmt, const nlohmann::ordered_json &result) {
    }

    void check_type_string(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameString *) stmt;
        ASSERT_EQ(str_stmt->charset.to_string(), result["charset"].get<std::string>());
        ASSERT_EQ(str_stmt->collate.to_string(), result["collate"].get<std::string>());
    }

    void check_type_timestamp(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameTimestamp *) stmt;
        ASSERT_EQ(pb::ast::TimeStampPrecision_Name(str_stmt->precision), result["precision"].get<std::string>());
    }

    void check_type_duration(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameDuration *) stmt;
        ASSERT_EQ(pb::ast::TimeStampPrecision_Name(str_stmt->precision), result["precision"].get<std::string>());
    }

    void check_type_datetime(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameDateTime *) stmt;
        ASSERT_EQ(pb::ast::TimeStampPrecision_Name(str_stmt->precision), result["precision"].get<std::string>());
    }

    void check_type_interval(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameInterval *) stmt;
        ASSERT_EQ(pb::ast::TimeStampPrecision_Name(str_stmt->precision), result["precision"].get<std::string>());
    }

    void check_type_map(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameMap *) stmt;
        auto key = result["key"].get<nlohmann::ordered_json>();
        auto value = result["value"].get<nlohmann::ordered_json>();
        check_type_stmt(str_stmt->key, key);
        check_type_stmt(str_stmt->value, value);
    }

    void check_type_set(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameSet *) stmt;
        auto key = result["key"].get<nlohmann::ordered_json>();
        check_type_stmt(str_stmt->key, key);
    }

    void check_type_tuple(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameTuple *) stmt;
        auto key = result["type_args"].get<std::vector<nlohmann::ordered_json>>();
        ASSERT_EQ(str_stmt->type_args.size(), key.size());
        for (size_t i = 0; i < key.size(); i++) {
            check_type_stmt(str_stmt->type_args[i], key[i]);
        }
    }

    void check_type_struct(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameStruct *) stmt;
        auto key = result["type_args"].get<std::vector<nlohmann::ordered_json>>();
        ASSERT_EQ(str_stmt->type_args.size(), key.size());
        for (size_t i = 0; i < key.size(); i++) {
            ASSERT_EQ(str_stmt->type_args[i]->name.to_string(), key[i]["key"].get<std::string>());
            check_type_stmt(str_stmt->type_args[i]->type, key[i]["type"].get<nlohmann::ordered_json>());
        }
    }

    void check_type_union(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameUnion *) stmt;
        auto key = result["type_args"].get<std::vector<nlohmann::ordered_json>>();
        ASSERT_EQ(str_stmt->type_args.size(), key.size());
        for (size_t i = 0; i < key.size(); i++) {
            ASSERT_EQ(str_stmt->type_args[i]->name.to_string(), key[i]["key"].get<std::string>());
            check_type_stmt(str_stmt->type_args[i]->type, key[i]["type"].get<nlohmann::ordered_json>());
        }
    }

    void check_type_enum(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameEnum *) stmt;
        auto uri = result["uri"].get<nlohmann::ordered_json>();
        //check_range_var(str_stmt->uri, uri);
        auto key = result["type_args"].get<std::vector<nlohmann::ordered_json>>();
        ASSERT_EQ(str_stmt->type_args.size(), key.size());
        for (size_t i = 0; i < key.size(); i++) {
            if (str_stmt->type_args[i]->value) {
                check_constant_expr_node_info(str_stmt->type_args[i]->value, key[i]["value"].get<nlohmann::ordered_json>());
            }
            ASSERT_EQ(str_stmt->type_args[i]->name.to_string(), key[i]["name"].get<std::string>());
        }
    }

    void check_type_proto(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        auto str_stmt = (TypeNameProto *) stmt;
        auto uri = result["uri"].get<nlohmann::ordered_json>();
        check_constant_expr_node_info(str_stmt->uri, uri);
    }


    void check_type_stmt(TypeStmt *stmt, const nlohmann::ordered_json &result) {
        check_stmt_node_info(stmt, result);
        ASSERT_EQ(stmt->names.size(), 1);
        ASSERT_EQ(stmt->names[0].to_string(), result["name"].get<std::string>());
        ASSERT_EQ(stmt->typeOid, result["typeOid"].get<uint32_t>());
        ASSERT_EQ(stmt->setof, result["setof"].get<bool>());
        ASSERT_EQ(stmt->pct_type, result["pct_type"].get<bool>());
        ASSERT_EQ(pb::ast::TypeNameType_Name(stmt->typename_type), result["typename_type"].get<std::string>());
        /// typmods
        auto mods = result["typmods"].get<std::vector<nlohmann::ordered_json> >();
        ASSERT_EQ(stmt->typmods.size(), mods.size());
        for (size_t i = 0; i < mods.size(); i++) {
            auto mod = stmt->typmods[i];
            auto &json_mod = mods[i];
            check_constant_expr_node_info(mod, json_mod);
        }

        auto bounds = result["array_bounds"].get<std::vector<nlohmann::ordered_json> >();
        ASSERT_EQ(stmt->array_bounds.size(), bounds.size());
        for (size_t i = 0; i < bounds.size(); i++) {
            auto bound = stmt->array_bounds[i];
            auto &json_bound = bounds[i];
            check_constant_expr_node_info(bound, json_bound);
        }
        switch (stmt->typename_type) {
            case pb::ast::TypeNameType::TYPE_NAME_NUMERIC:
                check_type_numeric(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_STRING:
                check_type_string(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_TIMESTAMP:
                check_type_timestamp(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_DURATION:
                check_type_duration(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_DATETIME:
                check_type_datetime(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_INTERVAL:
                check_type_interval(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_MAP:
                check_type_map(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_SET:
                check_type_set(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_TUPLE:
                check_type_tuple(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_STRUCT:
                check_type_struct(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_UNION:
                check_type_union(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_ENUM:
                check_type_enum(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_PROTO:
                check_type_proto(stmt, result);
                break;
            case pb::ast::TypeNameType::TYPE_NAME_JSON:
            case pb::ast::TypeNameType::TYPE_NAME_HLL:
            case pb::ast::TypeNameType::TYPE_NAME_BITMAP:
            case pb::ast::TypeNameType::TYPE_NAME_TDIGEST:
            case pb::ast::TypeNameType::TYPE_NAME_GENERIC:
                break;
            default:
                KCHECK(false) << "not implemented type:" << pb::ast::TypeNameType_Name(stmt->typename_type);
        }
    }
} // namespace cantor::parser
